﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
using CafeOnline.Common;
using CafeOnline.Common.Criteria;
using CafeOnline.MvcFramework.Composition;
using CafeOnline.MvcFramework.Grid;
using CafeOnline.MvcFramework.Extensions;

namespace CafeOnline.MvcFramework.Interfaces {
    public interface IGridFactory {

        #region Methods

        GridData GenerateGridData(string gridId, int? maxRows = null);

        object SearchData(string gridId, GridSettings settings);

        IGrid Instantiate(string id, GridOptions options);

        IGrid Get(string gridId);

        JavaScriptResult RefreshColumn(string gridId, List<string> visibleColumns);

        void SetColumnWidth(string gridId, string columnName, int width);

        #endregion Methods
    }
    [InheritedExport(typeof(IGridFactory))]
    public abstract class GridFactoryBase<EntityList, EntityType, Criteria> : IGridFactory, IPartImportsSatisfiedNotification
        where EntityType : BaseEntity
        where Criteria : BaseCriteria, new()
        where EntityList : IEnumerable<EntityType> {
        #region IFields

        private readonly object m_lock = new object();
        private Dictionary<string, IGrid> m_gridCollections;
#pragma warning disable 0649,0169
        [Import]
        protected GridProvider Provider;
        [Import]
        private ReportProvider ReportProvider { get; set; }
#pragma warning restore 0649,0169

        #endregion IFields
        #region IConstructors

        public GridFactoryBase() {
            m_gridCollections = new Dictionary<string, IGrid>();
        }

        #endregion IConstructors
        #region IProperties

        protected IGridFactoryMetadata Metadata {
            get {
                return MetadataExplorer.GetMetadata<IGridFactoryMetadata>(this.GetType());
            }
        }

        #endregion IProperties
        #region IMethods

        public void OnImportsSatisfied() {
            //TODO: Retrieve the columns there.
        }

        public IGrid Get(string gridId) {
            lock (m_lock) {
                IGrid ret;
                if (m_gridCollections.TryGetValue(gridId, out ret)) {
                    return ret;
                }
            }
            return null;
        }

        #region Data Methods

        /// <summary>
        /// Apply sorting on the criteria
        /// </summary>
        /// <param name="c">criteria to sort</param>
        /// <param name="column">column for sorting</param>
        /// <param name="direction">direction of the sorting</param>
        protected abstract void Sort(Criteria c, string column, SortOrder direction);

        /// <summary>
        /// Page the criteria to select only a subset
        /// </summary>
        /// <param name="c">criteria to page</param>
        /// <param name="take">number of items to return</param>
        /// <param name="skip">number of items to skip</param>
        protected abstract void Paging(Criteria c, int take, int skip);

        /// <summary>
        /// Returns the data found for specified criteria.
        /// </summary>
        /// <param name="c">criteria for filtering and paging</param>
        /// <returns>number of items found</returns>
        protected abstract EntityList FindData(Criteria c);

        /// <summary>
        /// Gets the total of entries that satisfies the criteria
        /// </summary>
        /// <param name="c">criteria for searching (no paging)</param>
        /// <returns>items found</returns>
        protected abstract int GetTotal(Criteria c);

        /// <summary>
        /// Performs a query to retrieved footer for the grid.
        /// </summary>
        /// <param name="c">criteria to be used</param>
        /// <returns>one entry that summarize the entries</returns>
        protected virtual EntityType FindFooter(Criteria c) {
            return null;
        }

        /// <summary>
        /// Applies a special filtering specified on the ViewData of the grid.
        /// </summary>
        /// <param name="c">criteria to filter</param>
        /// <param name="optionViewBag">grid viewbag</param>
        protected virtual void GridFiltering(Criteria c, dynamic optionViewBag) {
        }

        #endregion Data Methods

        /// <summary>
        /// Retrieves additional information that can be used to change behaviour of the entire row.
        /// By default, the system extract the id but when overriden, it can be used to add additional information.
        /// </summary>
        /// <param name="row">row on which the data has to be extracted</param>
        /// <param name="data">data to populate</param>
        protected virtual void PopulateRowData(EntityType row, IDictionary<string, object> data) {
            data.Add("id", row.EntityKey.Serialize());
        }


        /// <summary>
        /// Builds the criteria.
        /// </summary>
        /// <param name="gridId">The grid id.</param>
        /// <returns></returns>
        protected virtual Criteria BuildCriteria(string gridId) {
            return ReportProvider.GetGridCriteria<Criteria>(gridId);
        }

        /// <summary>
        /// Generates the grid data.
        /// </summary>
        /// <param name="gridId">The grid id.</param>
        /// <param name="ctx">The CTX.</param>
        /// <param name="maxRows">The max rows.</param>
        /// <returns></returns>
        public GridData GenerateGridData(string gridId, int? maxRows = null) {
            //Grid must have been initialized so created in order to 
            IGrid grid = Get(gridId);
            if (grid != null) {
                var criteria = BuildCriteria(gridId);
                //var gridFilterContext = ctx as IGridFilterContext<Criteria>;
                //if (gridFilterContext != null) {
                //    gridFilterContext.Filter(criteria);
                //}

                //TODO: using session maybe
                var settings = new GridSettings();//m_userModel.CreatePart().CustomData["gridSettings-" + gridId] as GridSettings;
                //ApplyGridSettingsOnCriteria(grid, criteria, settings);
                //if (maxRows.HasValue) {
                //    criteria.PrepareTopOnly(maxRows.Value);
                //} else {
                //    criteria.TopOnly = false;
                //    criteria.Paging = null;
                //}
                int total = GetTotal(criteria);
                var columns = grid.CurrentColumns.ToList();
                var rows = GetData(settings, grid, criteria, ref total);
                return new GridData(grid.ID, columns, rows);
            } else {
                return null;
            }
        }

        /// <summary>
        /// Applies the grid settings on criteria.
        /// </summary>
        /// <param name="grid">The grid.</param>
        /// <param name="criteria">The criteria.</param>
        /// <param name="settings">The settings.</param>
        private void ApplyGridSettingsOnCriteria(IGrid grid, Criteria criteria, GridSettings settings) {
            if (settings != null) {
                if (settings.IsSearch) {
                    foreach (Rule rule in settings.Where.rules) {
                        var c = from col in grid.CurrentColumns
                                where col.ColumnName.Equals(rule.field)
                                select col;
                        var column = c.FirstOrDefault();
                        if (column != null && column.Factory != null) {
                            column.Factory.FilterData(criteria, rule);
                        }
                    }
                }
            }
            if (!string.IsNullOrEmpty(settings.SortColumn)) {
                var col = grid.CurrentColumns.FirstOrDefault(x => x.ColumnName == settings.SortColumn);
                if (col != null && col.Factory != null) {
                    criteria.Sorting = new Sorting {
                        Column = col.Factory.Metadata.SortingName,
                        Direction = settings.SortOrder == "asc" ? SortDirection.Asc : SortDirection.Desc
                    };
                }
            }
            GridFiltering(criteria, grid.Options.ViewBag);
        }

        /// <summary>
        /// Searches the data.
        /// </summary>
        /// <param name="gridId">The grid id.</param>
        /// <param name="settings">The settings.</param>
        /// <param name="ctx">The CTX.</param>
        /// <returns></returns>
        public object SearchData(string gridId, GridSettings settings) {
            //Grid must have been initialized so created in order to 
            IGrid grid = Get(gridId);
            if (grid != null) {

                //IUser currentUser = m_userModel.CreatePart();

                //currentUser.CustomData["gridSettings-" + gridId] = settings;
                //var gridFilterContext = ctx as IGridFilterContext<Criteria>;

                var criteria = BuildCriteria(gridId);

                ApplyGridSettingsOnCriteria(grid, criteria, settings);
                Paging(criteria, settings.PageSize, (settings.PageIndex - 1) * settings.PageSize);


                int total = 0;
                var rows = GetData(settings, grid, criteria, ref total);
                object jsonData = null;
                if (grid.Options.FooterData) {
                    EntityType totalEntity = FindFooter(criteria);
                    if (totalEntity != null) {
                        ExpandoObject totalExpando = new ExpandoObject();
                        var dic = (IDictionary<string, object>)totalExpando;
                        grid.CurrentColumns.Where(t => t.Factory != null).ToList().ForEach(c => dic.Add(c.ColumnName, c.Factory.GetData(totalEntity, dic)));
                        jsonData = new {
                            total = (int)Math.Ceiling((total * 1.0) / settings.PageSize),
                            page = settings.PageIndex,
                            records = total,
                            rows = rows.ToArray(),
                            userdata = totalExpando
                        };
                    }
                }

                if (jsonData == null) {
                    jsonData = new {
                        total = (int)Math.Ceiling((1.0 * total) / settings.PageSize),
                        page = settings.PageIndex,
                        records = total,
                        rows = rows.ToArray()
                    };
                }
                return jsonData;
            } else {
                return null;
            }
        }

        /// <summary>
        /// Gets the data.
        /// </summary>
        /// <param name="settings">The settings.</param>
        /// <param name="ctx">The CTX.</param>
        /// <param name="grid">The grid.</param>
        /// <param name="criteria">The criteria.</param>
        /// <param name="total">The total.</param>
        /// <returns></returns>
        private List<ExpandoObject> GetData(GridSettings settings, IGrid grid, Criteria criteria, ref int total) {
            IEnumerable<EntityType> list = FindData(criteria);
            total = GetTotal(criteria);
            //var gridMergeContext = ctx as IGridMergeContext<EntityType>;


            var rows = new List<ExpandoObject>();
            list.ForEach((entity) => {
                var eOb = new ExpandoObject();
                var dic = (IDictionary<string, object>)eOb;
                PopulateRowData(entity, dic);
                grid.CurrentColumns.Where(t => t.Factory != null).ForEach(c => dic.Add(c.ColumnName, c.Factory.GetData(entity, dic)));
                rows.Add(eOb);
            });
            return rows;
        }

        protected abstract IGrid Create(string gridType, string gridId, IEnumerable<IColumnFactory> columns, GridCustomSettings gridCustomSettings, GridOptions options);

        /// <summary>
        /// Inits the default cons.
        /// </summary>
        /// <param name="columns">The columns.</param>
        /// <param name="gridId">The grid id.</param>
        /// <returns></returns>
        private GridCustomSettings InitDefaultCons(IEnumerable<IColumnFactory> columns, string gridId) {
            var gc = new GridCustomSettings();
            var cols = gc.Columns;
            if (cols == null) {
                cols = new List<ColumnCustomSettings>();
            }
            //Add the missing columns
            var addCols = columns.Select(c => new ColumnCustomSettings() {
                Name = c.Metadata.ColumnName,
                Visible = !c.Metadata.Hidden,
                Width = null
            }).ToList();
            gc.Columns = cols.Union(addCols).ToList();
            return gc;
        }

        /// <summary>
        /// Instantiates the specified grid id.
        /// </summary>
        /// <param name="gridId">The grid id.</param>
        /// <param name="options">The options.</param>
        /// <returns></returns>
        public IGrid Instantiate(string gridId, GridOptions options) {
            lock (m_lock) {
                IGrid ret;
                if (!m_gridCollections.TryGetValue(gridId, out ret)) {
                    var columns = Provider.GetColumns(Metadata.GridTypeName).Select(t => t.Value);
                    var gcsv = InitDefaultCons(columns, gridId);
                    ret = Create(Metadata.GridTypeName, gridId, columns, gcsv, options);
                    ret.Initialize(options);
                    m_gridCollections.Add(gridId, ret);
                }
                return ret;
            }
        }

        /// <summary>
        /// Refreshes the column.
        /// </summary>
        /// <param name="gridId">The grid id.</param>
        /// <param name="columns">The columns.</param>
        /// <returns></returns>
        public JavaScriptResult RefreshColumn(string gridId, List<string> columns) {
            IGrid g = Get(gridId);
            if (g != null) {
                if (columns != null && columns.Count > 0) {
                    g.RefreshColumn(columns);
                }
            }
            return g.ReloadGrid();
        }
        /// <summary>
        /// Sets the width of the column.
        /// </summary>
        /// <param name="gridId">The grid id.</param>
        /// <param name="columnName">Name of the column.</param>
        /// <param name="width">The width.</param>
        public void SetColumnWidth(string gridId, string columnName, int width) {
            IGrid g = Get(gridId);
            if (g != null) {
                g.SetColumnWidth(columnName, width);

            }
        }


        #endregion IMethods
    }
    public interface IGridFactoryMetadata {
        string GridTypeName {
            get;
        }
    }
}
